#include "glee.h"
#include "sphereProject.h"
#include "math.h"
#include "drawing.h"
#include "common.h"
#include "shaderManager.h"

namespace Tmpl {

void Vertex::NormalizeTexValues()
{
	float len = 0.00001f;
	for (int i=0; i<MAX_TEXTURE_COUNT; ++i) len += texValues[i] * texValues[i];
	float rlen = 1.0f / sqrtf(len);
	for (int i=0; i<MAX_TEXTURE_COUNT; ++i) texValues[i] *= rlen;
}

Sphere::Sphere(int size, float tiling)
{
	mTextureCount = 0;
	mSize = size;
	mVertices = MALLOC64(mSize * mSize, Vertex);

	// Load the shader
	mShaderFlat = ShaderManager::LoadShader("Data/RenderFlat");
	mShader = ShaderManager::LoadShader("Data/Terrain");

	// Load Textures
	AddTexture("Data/DarkRock.png");
	AddTexture("Data/Dirt.png");
	/*
	AddTexture("Data/Erde2.png");
	AddTexture("Data/Erde4.png");
	AddTexture("Data/Erde6.png");
	AddTexture("Data/Grass01.png");
	AddTexture("Data/Grass.png");
	AddTexture("Data/grayrock.png");
	AddTexture("Data/moltenrock.png");
	AddTexture("Data/moltenrock2.png");
	AddTexture("Data/road.png");
	AddTexture("Data/rock.png");
	AddTexture("Data/sand01.png");
	AddTexture("Data/snow.png");
	AddTexture("Data/solidstones.png");
	AddTexture("Data/stoneroad.png");
	AddTexture("Data/summergrass.png");
	*/

	// Create the plane
	float stepUV = 1.0f / float(mSize-1);
	float step = 2.0f / float(size-1);
	for (int y=0; y<mSize; ++y)
	for (int x=0; x<mSize; ++x)
	{
		float fX = float(x);
		float fY = float(y);

		Vec2f uv = Vec2f(stepUV*x,stepUV*y) * tiling;

		float rX = -1.0f + step * fX;
		float rZ = -1.0f + step * fY;
		float rY = (rX*rX*PI + rZ*rZ*PI) / -10.0f;
		Vec3f pos = Vec3f((rX+rX*rY) * 10.0f, rY, (rZ+rZ*rY) * 10.0f);

		mVertices[y * mSize + x] = Vertex(pos, uv);

		// update tex values
		Vertex& v = mVertices[y * mSize + x];
		for (int i=0; i<MAX_TEXTURE_COUNT; ++i) v.texValues[i] = 0.0f;
		float left = 1.0f;
		for (int i=rand() % mTextureCount; i<mTextureCount; ++i)
		{
			float val = Clamp(0.0f, left, rand() % (1119-x+y) * 0.001f);
			left -= val;
			v.texValues[i] = val;
		}
		v.NormalizeTexValues();
	}

	// Create the VBO
	glGenBuffers(1, &mVBO);
	glBindBuffer(GL_ARRAY_BUFFER, mVBO);
	glBufferData(GL_ARRAY_BUFFER, mSize*mSize*sizeof(Vertex), 0, GL_STREAM_DRAW);
	UpdateVBO();

	// Generate Indices VBO
	int idx = 0;
	glGenBuffers(1, &mVBOIndices);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIndices);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, (mSize-1)*(mSize-1)*4*sizeof(GLuint), 0, GL_STATIC_DRAW);
	GLuint* ibuffer = (GLuint*)glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);
	for (int y=0; y<mSize-1; ++y)
	for (int x=0; x<mSize-1; ++x)
	{
		ibuffer[idx++] = (y  ) * mSize + (x  );
		ibuffer[idx++] = (y  ) * mSize + (x+1);
		ibuffer[idx++] = (y+1) * mSize + (x+1);
		ibuffer[idx++] = (y+1) * mSize + (x  );
	}
	glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); 
}

int Sphere::AddTexture(const char* filename)
{
	mTextures[mTextureCount] = new Image();
	mTextures[mTextureCount]->Create(filename);
	mTextures[mTextureCount]->BuildTex();
	return mTextureCount++;
}
void Sphere::Update(WorldMap* map, Vec3f playerPos)
{

	UpdateVBO();
}

void Sphere::UpdateVBO()
{
	int idx = 0;
	glBindBuffer(GL_ARRAY_BUFFER, mVBO);
	float* fbuffer = (float*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
	for (int y=0; y<mSize; ++y)
	for (int x=0; x<mSize; ++x)
	{
		Vertex* v = &mVertices[y*mSize+x];

		fbuffer[idx++] = v->pos.x;
		fbuffer[idx++] = v->pos.y;
		fbuffer[idx++] = v->pos.z;
		fbuffer[idx++] = v->normal.x;
		fbuffer[idx++] = v->normal.y;
		fbuffer[idx++] = v->normal.z;
		fbuffer[idx++] = v->uv.x;
		fbuffer[idx++] = v->uv.y;
		for (int i=0; i<MAX_TEXTURE_COUNT; ++i)	fbuffer[idx++] = v->texValues[i];
	}
	glUnmapBuffer(GL_ARRAY_BUFFER); 
}

void Sphere::BindVBO(ShaderProgram* shader)
{
	GLint vertexLoc = shader->GetAttribLoc("InVertex");
	GLint normalLoc = shader->GetAttribLoc("InNormal");
	GLint texCoord0Loc = shader->GetAttribLoc("InTexCoord0");

	if (vertexLoc >= 0) glEnableVertexAttribArray(vertexLoc);
	if (normalLoc >= 0) glEnableVertexAttribArray(normalLoc);
	if (texCoord0Loc >= 0) glEnableVertexAttribArray(texCoord0Loc);

	glBindBuffer(GL_ARRAY_BUFFER, mVBO);
	if (vertexLoc >= 0) glVertexAttribPointer(vertexLoc, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
	if (normalLoc >= 0) glVertexAttribPointer(normalLoc, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)((char*)NULL+sizeof(float)*3));
	if (texCoord0Loc >= 0) glVertexAttribPointer(texCoord0Loc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)((char*)NULL+sizeof(float)*6));
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIndices);
}

void Sphere::UnbindVBO(ShaderProgram* shader)
{
	GLint vertexLoc = shader->GetAttribLoc("InVertex");
	GLint normalLoc = shader->GetAttribLoc("InNormal");
	GLint texCoord0Loc = shader->GetAttribLoc("InTexCoord0");
	if (vertexLoc >= 0) glDisableVertexAttribArray(vertexLoc);
	if (normalLoc >= 0) glDisableVertexAttribArray(normalLoc);
	if (texCoord0Loc >= 0) glDisableVertexAttribArray(texCoord0Loc);
}

void Sphere::RenderFlat(Matrix4x4f projection, Matrix4x4f modelView)
{
	mShaderFlat->Use();
	mShaderFlat->UniformMatrix4x4("matProjModelView", projection * modelView);

	BindVBO(mShaderFlat);

	glDrawElements(GL_QUADS, (mSize-1)*(mSize-1)*4, GL_UNSIGNED_INT, 0);

	UnbindVBO(mShaderFlat);
}

void Sphere::RenderTextured(Matrix4x4f projection, Matrix4x4f modelView)
{
	mShader->Use();
	mShader->Uniform1i("TerrainTex1", 0);
	mShader->Uniform1i("TerrainTex2", 1);
	mShader->UniformMatrix4x4("matProjModelView", projection * modelView);
	mShader->UniformMatrix4x4("matModelView", modelView);

	BindVBO(mShader);
	GLint texValueLoc = mShader->GetAttribLoc("InTexValue");
	glEnableVertexAttribArray(texValueLoc);

	glEnable(GL_BLEND);
	glBlendFunc(GL_ONE, GL_ONE);
	for (int i=0; i<mTextureCount; i+=2)
	{
		glActiveTexture(GL_TEXTURE1);
		if (mTextureCount > i+1)
		{
			glBindTexture(GL_TEXTURE_2D, mTextures[i+1]->GetTexId());
			glEnable(GL_TEXTURE_2D);
		}
		else
		{
			glBindTexture(GL_TEXTURE_2D, 0);
			glDisable(GL_TEXTURE_2D);
		}

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, mTextures[i]->GetTexId());
		glEnable(GL_TEXTURE_2D);

		glVertexAttribPointer(texValueLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)((char*)NULL+sizeof(float)*(8+i)));
		glDrawElements(GL_QUADS, (mSize-1)*(mSize-1)*4, GL_UNSIGNED_INT, 0);
	}
	glDisable(GL_BLEND);

	glDisableVertexAttribArray(texValueLoc);
	UnbindVBO(mShader);
}
void Sphere::Render(Matrix4x4f projection, Matrix4x4f modelView)
{     
	// First pass, leave behind a depth entry
	glColorMask(false, false, false, false);
	RenderFlat(projection, modelView);

	// Second Pass, render the real geometry
	glColorMask(true, true, true, true);
	glDepthFunc(GL_EQUAL);
	RenderTextured(projection, modelView);
	glDepthFunc(GL_LEQUAL);

	// Were done
	mShader->UnUse();

	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, 0);
	glDisable(GL_TEXTURE_2D);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, 0);
	glDisable(GL_TEXTURE_2D);

	//glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
}

}; // namespace Tmpl